#include "scanner.h"
#include "gnum.h"
#include <ctype.h>

namespace cnc {

	namespace compiler {

		Scanner::Scanner() : er_(0)
		{
		}

		void Scanner::setErrorReporter(ErrorReporter *er)
		{
			er_ = er;
		}

		const char *Scanner::getLeader()
		{
			int c;

			resetLexeme();
			do {
				c = getChar();
				if (c != '\n' && c != '\r' && c != EOF)
					addCharToLexeme(c);
			} while (c != '\n' && c != '\r' && c != EOF);
			do
				c = getChar();
			while (c == '\n' || c == '\r');
			putBackChar(c);
			return getLastLexeme();
		}

		Token Scanner::getToken()
		{
			enum {
				START, ACCEPT_PROGRAM_NUMBER, ACCEPT_ADDRESS,
				ACCEPT_UNSIGNED_INTEGER, ACCEPT_UNSIGNED_FLOAT,
				NO_LEADING_DIGITS, POSITIVE_SIGN, NO_LEADING_DIGITS_EITHER,
				ACCEPT_NEGATIVE_FLOAT, NEGATIVE_SIGN,
				ACCEPT_NEGATIVE_INTEGER, READ_COMMENT, ACCEPT_COMMENT,
				ACCEPT_BLOCK_DELETE, ACCEPT_WHITE_SPACE, ACCEPT_TX_CODE,
				ACCEPT_EOB, ACCEPT_EOL, ACCEPT_EOF,
				ACCEPT_NONE
			} state = START;
			int c;
			GCodeNumber number;
			TokenErrorID error = ERROR_NONE;

			resetLexeme();
			while ("no token found") {
				switch (state) {
				case START:
					// exit
					c = getChar();
					switch (c) {
					case 'O':
					case 'o':
					case ':':
						state = ACCEPT_PROGRAM_NUMBER;
						break;
					case '.':
						state = NO_LEADING_DIGITS;
						break;
					case '+':
						state = POSITIVE_SIGN;
						break;
					case '-':
						state = NEGATIVE_SIGN;
						break;
					case '(':
						state = READ_COMMENT;
						break;
					case '/':
						state = ACCEPT_BLOCK_DELETE;
						break;
					case ' ':
					case '\t':
						state = ACCEPT_WHITE_SPACE;
						break;
					case '%':
						state = ACCEPT_TX_CODE;
						break;
					case ';':
						state = ACCEPT_EOB;
						break;
					case '\n':
						state = ACCEPT_EOL;
						break;
					case EOF:
						state = ACCEPT_EOF;
						break;
					default:
						if (isalpha(c)) {
							state = ACCEPT_ADDRESS;
						} else if (isdigit(c)) {
							state = ACCEPT_UNSIGNED_INTEGER;
						} else {
							er_->report(ERRORCLS_ERROR, ERRORMSG_UNKNOWN_CHAR,
								"", getRow(), getColumn());
							error = ERROR_UNKNOWN_CHAR;
							state = ACCEPT_NONE;
						}
					}
					break;

				case ACCEPT_PROGRAM_NUMBER:
					// exit
					return Token(TOKEN_PROGRAM_NUMBER, c);

				case ACCEPT_ADDRESS:
					// exit
					return Token(TOKEN_ADDRESS, c);

				case ACCEPT_UNSIGNED_INTEGER:
					// enter
					if (number.addDigit(c - '0') == 0)
						error = ERROR_LENGTH;
					// exit
					c = getChar();
					if (isdigit(c))
						state = ACCEPT_UNSIGNED_INTEGER;
					else if (c == '.')
						state = ACCEPT_UNSIGNED_FLOAT;
					else {
						putBackChar(c);
						return Token(TOKEN_UINT, number, error);
					}
					break;

				case ACCEPT_UNSIGNED_FLOAT:
					// enter
					if (isdigit(c)) {
						if (number.addDigit(c - '0') == 0)
							error = ERROR_LENGTH;
					} else if (c == '.')
						number.beginFraction();
					// exit
					c = getChar();
					if (isdigit(c))
						state = ACCEPT_UNSIGNED_FLOAT;
					else {
						putBackChar(c);
						return Token(TOKEN_UFLOAT, number, error);
					}
					break;

				case NO_LEADING_DIGITS:
					// entry
					number.beginFraction();
					// exit
					c = getChar();
					if (isdigit(c))
						state = ACCEPT_UNSIGNED_FLOAT;
					else {
						er_->report(ERRORCLS_ERROR, ERRORMSG_DIGIT_EXPECTED,
								"", getRow(), getColumn());
						error = ERROR_DIGIT_EXPECTED;
						state = ACCEPT_NONE;
					}
					break;

				case POSITIVE_SIGN:
					// exit
					c = getChar();
					if (isdigit(c))
						state = ACCEPT_UNSIGNED_INTEGER;
					else if (c == '.')
						state = NO_LEADING_DIGITS;
					else {
						er_->report(ERRORCLS_ERROR, ERRORMSG_NUMBER_EXPECTED,
								"", getRow(), getColumn());
						error = ERROR_NUM_EXPECTED;
						state = ACCEPT_NONE;
					}
					break;

				case NO_LEADING_DIGITS_EITHER:
					// entry
					number.beginFraction();
					// exit
					c = getChar();
					if (isdigit(c))
						state = ACCEPT_NEGATIVE_FLOAT;
					else {
						er_->report(ERRORCLS_ERROR, ERRORMSG_DIGIT_EXPECTED,
								"", getRow(), getColumn());
						error = ERROR_DIGIT_EXPECTED;
						state = ACCEPT_NONE;
					}
					break;

				case ACCEPT_NEGATIVE_FLOAT:
					// enter
					if (isdigit(c)) {
						if (number.addDigit(c - '0') == 0)
							error = ERROR_LENGTH;
					} else if (c == '.')
						number.beginFraction();
					// exit
					c = getChar();
					if (isdigit(c))
						state = ACCEPT_UNSIGNED_FLOAT;
					else {
						putBackChar(c);
						return Token(TOKEN_NFLOAT, -number, error);
					}
					break;

				case NEGATIVE_SIGN:
					// exit
					c = getChar();
					if (isdigit(c))
						state = ACCEPT_NEGATIVE_INTEGER;
					else if (c == '.')
						state = NO_LEADING_DIGITS_EITHER;
					else {
						er_->report(ERRORCLS_ERROR, ERRORMSG_NUMBER_EXPECTED,
								"", getRow(), getColumn());
						error = ERROR_NUM_EXPECTED;
						state = ACCEPT_NONE;
					}
					break;

				case ACCEPT_NEGATIVE_INTEGER:
					// enter
					if (number.addDigit(c - '0') == 0)
						error = ERROR_LENGTH;
					// exit
					c = getChar();
					if (isdigit(c))
						state = ACCEPT_NEGATIVE_INTEGER;
					else if (c == '.')
						state = ACCEPT_NEGATIVE_FLOAT;
					else {
						putBackChar(c);
						return Token(TOKEN_NINT, -number, error);
					}
					break;

				case READ_COMMENT:
					// exit
					c = getChar();
					switch (c) {
					case EOF:
						er_->report(ERRORCLS_ERROR, ERRORMSG_EOF_UNEXPECTED,
								"", getRow(), getColumn());
						error = ERROR_EOF_UNEXPECTED;
					case ')':
						state = ACCEPT_COMMENT;
						break;
					default:
						addCharToLexeme(c);
					}
					break;

				case ACCEPT_COMMENT:
					return Token(TOKEN_COMMENT, 0, error);

				case ACCEPT_BLOCK_DELETE:
					return Token(TOKEN_BLOCK_DELETE, c);

				case ACCEPT_WHITE_SPACE:
					// exit
					c = getChar();
					if (' ' != c && '\t' != c) {
						putBackChar(c);
						return Token(TOKEN_WHITE_SPACE, 0);
					}
					break;

				case ACCEPT_TX_CODE:
					return Token(TOKEN_TX_CODE, c);

				case ACCEPT_EOB:
					return Token(TOKEN_EOB, c);

				case ACCEPT_EOL:
					return Token(TOKEN_EOL, 0);

				case ACCEPT_EOF:
					return Token(TOKEN_EOF, EOF);

				case ACCEPT_NONE:
					return Token(TOKEN_NONE, 0, error);

				}
			}
		}

	} // namespace compiler

} // namespace cnc

